import {ElLoading, ElMessage, ElNotification, ElTree, type FormInstance, type TableColumnCtx} from 'element-plus'
import {nextTick, ref, type VNode} from 'vue'
import type {LoadingInstance} from 'element-plus/lib/components/loading/src/loading'
import {cloneDeep, remove, set, snakeCase, unset} from 'lodash-es'
import {useBusinessCodeStore} from '@/stores/modules/businessCode.store'
import type {RuleItemValidator} from '@/model/global-import'
import {i18n} from '@/lang'
import {validateUsername} from '@/utils/validateUtil'
import Decimal from 'decimal.js'
import {isProd} from '@/utils/viteUtil'
import type Node from 'element-plus/es/components/tree/src/model/node'

const SIGN_REGEXP: RegExp = new RegExp('([yMdhmsS])(\\1*)', 'g')
const DEFAULT_PATTERN: string = 'yyyy-MM-dd hh:mm:ss'
const loading = ref<LoadingInstance>()

/**
 * 判断变量是否存在<br/>
 * ''、null、undefined：返回false<br/>
 * 其他情况均返回true（数字0也返回true）
 * @param data
 */
export const exist: (data: any) => boolean = data => {
	return data !== undefined && data !== null && data !== ''
}

/**
 * 获取@/assets/image下的图片路径
 * @param path
 */
export const getImage: (path: string) => string = path => {
	// 资源路径必须使用相对路径，以避免项目打包后无法找到资源
	return new URL(`../assets/image/${path}`, import.meta.url).href
}

/**
 * 获取@/assets下的资源路径
 * @param path
 */
export const getAssets: (path: string) => string = path => {
	return new URL(path, import.meta.url).href
}

/**
 * focus表单验证失败的第一个元素
 * @param elementSelectors
 */
export const focusFormErrorElement: (...elementSelectors: string[]) => void = (...elementSelectors) => {
	try {
		nextTick().then(() => {
			const errorElements: Element[] = []
			if (elementSelectors && elementSelectors.length > 0) {
				for (const selector of elementSelectors) {
					const elements = document.querySelectorAll(selector)
					elements.forEach(element => {
						const isError = element.getElementsByClassName('is-error')
						for (let i = 0; i < isError.length; i++) {
							errorElements.push(isError[i])
						}
					})
				}
			} else {
				const isError = document.getElementsByClassName('is-error')
				for (let i = 0; i < isError.length; i++) {
					errorElements.push(isError[i])
				}
			}
			if (errorElements.length > 0) {
				const input = errorElements[0].getElementsByTagName('input')[0]
				if (input) {
					input.focus()
					const dropdown = input.closest('.el-select')
					if (dropdown) {
						const dropdownInstance = (<any>dropdown).__vue__ || (<any>dropdown).__vueParentComponent?.proxy
						dropdownInstance && dropdownInstance.toggleMenu()
					}
				} else {
					let textarea = errorElements[0].getElementsByTagName('textarea')[0]
					if (textarea) {
						textarea.focus()
					}
				}
			}
		})
	} catch (e) {
		console.error('focusItem()方法发生错误')
	}
}

export const notifySuccess: (msg?: string | VNode, milliseconds?: number, callback?: Function) => void = (msg = i18n.global.t('notifyDefault.success'), milliseconds = 3000, callback) => {
	ElNotification.success({
		title: i18n.global.t('notifyTitle.success'),
		message: msg,
		duration: milliseconds,
		position: 'bottom-right',
		onClose: function () {
			callback && callback()
		}
	})
}

export const notifyWarning: (msg?: string | VNode, milliseconds?: number, callback?: Function) => void = (msg = i18n.global.t('notifyDefault.warning'), milliseconds = 3000, callback) => {
	ElNotification.warning({
		title: i18n.global.t('notifyTitle.warning'),
		message: msg,
		duration: milliseconds,
		position: 'bottom-right',
		onClose: function () {
			callback && callback()
		}
	})
}

export const notifyError: (msg?: string | VNode, milliseconds?: number, callback?: Function) => void = (msg = i18n.global.t('notifyDefault.error'), milliseconds = 3000, callback) => {
	ElNotification.error({
		title: i18n.global.t('notifyTitle.error'),
		message: msg,
		duration: milliseconds,
		position: 'bottom-right',
		onClose: function () {
			callback && callback()
		}
	})
}

export const notifyInfo: (msg?: string | VNode, milliseconds?: number, callback?: Function) => void = (msg = i18n.global.t('notifyDefault.info'), milliseconds = 3000, callback) => {
	ElNotification.info({
		title: i18n.global.t('notifyTitle.info'),
		message: msg,
		duration: milliseconds,
		position: 'bottom-right',
		onClose: function () {
			callback && callback()
		}
	})
}

export const messageSuccess: (msg?: string | VNode | (() => VNode), milliseconds?: number, callback?: Function) => void = (msg = i18n.global.t('notifyDefault.success'), milliseconds = 3000, callback) => {
	ElMessage.success({
		message: msg,
		duration: milliseconds,
		showClose: true,
		grouping: true,
		onClose: function () {
			callback && callback()
		}
	})
}

export const messageError: (msg?: string | VNode | (() => VNode), milliseconds?: number, callback?: Function) => void = (msg = i18n.global.t('notifyDefault.error'), milliseconds = 3000, callback) => {
	ElMessage.error({
		message: msg,
		duration: milliseconds,
		showClose: true,
		grouping: true,
		onClose: function () {
			callback && callback()
		}
	})
}

export const messageWarning: (msg?: string | VNode | (() => VNode), milliseconds?: number, callback?: Function) => void = (msg = i18n.global.t('notifyDefault.warning'), milliseconds = 3000, callback) => {
	ElMessage.warning({
		message: msg,
		duration: milliseconds,
		showClose: true,
		grouping: true,
		onClose: function () {
			callback && callback()
		}
	})
}

export const messageInfo: (msg?: string | VNode | (() => VNode), milliseconds?: number, callback?: Function) => void = (msg = i18n.global.t('notifyDefault.info'), milliseconds = 3000, callback) => {
	ElMessage.info({
		message: msg,
		duration: milliseconds,
		showClose: true,
		grouping: true,
		onClose: function () {
			callback && callback()
		}
	})
}

/**
 * 使el-table的序号支持多页，而不是每页都从1开始
 * @param cusPagination
 * @param index
 */
export const initTableIndex: (cusPagination: any, index: number) => number | undefined = (cusPagination, index) => {
	if (cusPagination.$attrs.loading || index < 0) {
		return undefined
	}
	return (cusPagination.page - 1) * cusPagination.pageSize_ + index + 1
}

export const filters_method: (value: any, row: any, column: any) => boolean = (value, row, column) => {
	const property = column['property']
	return row[property] === value
}

/**
 * 将treeArray压成一层，返回一个新数组，不改变原treeArray<br/>
 * 深度优先<br/>
 * @param roots 原treeArray
 * @param childrenAttrName 下级属性名
 * @param unsetChildren 是否删除下级属性
 */
export const treeArrayToArray: <T extends AnyObject> (roots: T[], childrenAttrName?: string, unsetChildren?: boolean) => T[] = <T extends AnyObject>(roots: T[], childrenAttrName = 'children', unsetChildren = true) => {
	if (!roots) {
		return []
	}
	let array: T[] = []
	for (const node of roots) {
		const clonedNode = cloneDeep(node)
		if (unsetChildren) {
			unset(clonedNode, childrenAttrName)
		}
		array.push(clonedNode)
		if (node[childrenAttrName] && node[childrenAttrName].length > 0) {
			const res = treeArrayToArray(<T[]>node[childrenAttrName], childrenAttrName)
			array = array.concat(res)
		}
	}
	return array
}

/**
 * 遍历组件el-tree的所有节点，对每个节点执行handler，handler的参数为当前节点<br/>
 * 深度优先<br/>
 * @param tree
 * @param handler
 */
export const elTreeIterator: (tree: InstanceType<typeof ElTree>, handler: Consumer<Node>) => void = (tree, handler) => {
	if (!tree || !tree.root || !tree.root.childNodes) {
		return
	}
	treeArrayIterator(tree.root.childNodes, handler, 'childNodes')
}

/**
 * 从根节点开始遍历tree，对每个节点执行handler，handler的参数为当前节点<br/>
 * 深度优先<br/>
 * @param root
 * @param handler
 * @param childrenFieldName
 */
export const treeIterator: <T extends AnyObject> (root: T, handler: Consumer<T>, childrenFieldName?: string) => void = (root, handler, childrenFieldName = 'children') => {
	if (!root) {
		return
	}
	treeArrayIterator([root], handler, childrenFieldName)
}

/**
 * 遍历treeArray，对每个节点执行handler，handler的参数为当前节点<br/>
 * 深度优先<br/>
 * @param roots
 * @param handler
 * @param childrenFieldName
 */
export const treeArrayIterator: <T extends AnyObject> (roots: T[], handler: Consumer<T>, childrenFieldName?: string) => void = (roots, handler, childrenFieldName = 'children') => {
	if (!roots) {
		return
	}
	for (const tree of roots) {
		handler(tree)
		treeArrayIterator(tree[childrenFieldName], handler, childrenFieldName)
	}
}

/**
 * 遍历treeArray，转换每个节点<br/>
 * 返回一个新的treeArray，不改变原treeArray<br/>
 * 深度优先<br/>
 * @param roots
 * @param mapper
 * @param childrenFieldName
 */
export const treeArrayMapper = <T extends AnyObject, R extends AnyObject>(roots: T[], mapper: Functional<T, R>, childrenFieldName: string = 'children'): R[] => {
	if (!roots) {
		return []
	}
	const array: R[] = []
	for (const tree of roots) {
		const newNode = mapper(tree)
		array.push(newNode)
		set(newNode, childrenFieldName, [])
		newNode[childrenFieldName].push(...treeArrayMapper(tree[childrenFieldName], mapper, childrenFieldName))
	}
	return array
}

/**
 * 从treeArray中查找符合条件的节点，返回第一个符合条件的节点，如果没有找到则返回undefined<br/>
 * 深度优先<br/>
 * @param roots
 * @param predicate
 * @param childrenFieldName
 */
export const findInTreeArray: <T extends AnyObject> (roots: T[], predicate: Predicate<T>, childrenFieldName?: string) => T | undefined = (roots, predicate, childrenFieldName = 'children') => {
	if (!roots) {
		return
	}
	for (const node of roots) {
		if (predicate(node)) {
			return node
		}
		if (Array.isArray(node[childrenFieldName])) {
			const find = findInTreeArray(node[childrenFieldName], predicate, childrenFieldName)
			if (find) {
				return find
			}
		}
	}
}

/**
 * 从treeArray中查找所有符合条件的节点，如果没有找到则返回空数组<br/>
 * 深度优先<br/>
 * @param roots
 * @param predicate
 * @param childrenFieldName
 */
export const findAllInTreeArray = <T extends AnyObject>(roots: T[], predicate: Predicate<T>, childrenFieldName: string = 'children') => {
	if (!roots) {
		return []
	}
	let findArray: T[] = []
	for (const node of roots) {
		if (predicate(node)) {
			findArray.push(node)
		}
		if (Array.isArray(node[childrenFieldName])) {
			const find = findAllInTreeArray(node[childrenFieldName], predicate, childrenFieldName)
			if (find.length > 0) {
				findArray = findArray.concat(find)
			}
		}
	}
	return findArray
}

/**
 * 从给定的node开始，向上遍历每个父节点，直到根节点<br/>
 * 注意：只会一直向上寻找父节点，而不保证遍历所有节点
 * @param node
 * @param handler
 * @param parentFieldName
 */
export const treeArrayIteratorUp: <T extends AnyObject> (node: T, handler: Consumer<T>, parentFieldName?: string) => void = (node, handler, parentFieldName = 'parent') => {
	if (!node) {
		return
	}
	handler(node)
	treeArrayIteratorUp(node[parentFieldName], handler, parentFieldName)
}

/**
 * 获取某一种业务编码的全部数据
 * @param codeType
 */
export const getBusinessCodeData = (codeType: string): BusinessCodeTreeNode[] => {
	return useBusinessCodeStore().businessCodeCollection[codeType]
}

/**
 * 获取业务编码的名称
 * @param codeType 业务编码
 * @param value 业务编码取值
 * @param hideValueFlag 是否隐藏取值
 * @param fullPathValueFlag 入参value是否为全路径形式（包含上级的value，用“.”隔开），只有级联选择器才会用到
 */
export const getBusinessCodeName: (codeType: string, value: Arrayable<string | number>, hideValueFlag?: boolean, fullPathValueFlag?: boolean) => string | undefined = (codeType, value, hideValueFlag = true, fullPathValueFlag = false) => {
	if (!exist(codeType) || !exist(value)) {
		return
	}
	const list = useBusinessCodeStore().businessCodeCollection[codeType]
	if (!list) {
		return
	}
	// 多选模式
	if (Array.isArray(value)) {
		if (value.length > 0) {
			const labelArray: string[] = []
			for (const valueElement of value) {
				const label = getBusinessCodeName(codeType, valueElement, hideValueFlag, fullPathValueFlag)
				label && labelArray.push(label)
			}
			return labelArray.join(', ')
		} else {
			return
		}
	}
	// 单选模式
	else {
		// 将入参value转为string类型
		if (typeof value !== 'string') {
			value = String(value)
		}
		// 处理JSON数组
		if (value.startsWith('[') && value.endsWith(']')) {
			value = JSON.parse(value)
			return getBusinessCodeName(codeType, value, hideValueFlag, fullPathValueFlag)
		}
		const find = findInTreeArray(list, item => {
			if (fullPathValueFlag) {
				return value === item.value
			} else {
				// 构建fullPathValue
				let fullPathValues: string[] = []
				// 找到所有叶子结点
				const leafNodes = findAllInTreeArray(list, item => {
					return !item.children || item.children.length === 0
				})
				const currentNode = leafNodes.find(leafNode => {
					const split = leafNode.value.split('.')
					return split[split.length - 1] === value
				})
				if (!currentNode) {
					return false
				}
				// 从当前结点开始向上遍历，直到根结点
				treeArrayIteratorUp(currentNode, node => {
					fullPathValues.unshift(node.value)
				})
				return fullPathValues.join('.') === item.value
			}
		})
		if (!find) {
			return
		}
		if (hideValueFlag) {
			return find.label
		} else {
			return value + ' : ' + find.label
		}
	}
}

/**
 * 开启全屏loading
 */
export const showLoading: (message?: string) => LoadingInstance = (message = '正在加载请稍候') => {
	if (loading.value) {
		loading.value?.setText(message)
	} else {
		loading.value = ElLoading.service({
			lock: true,
			fullscreen: true,
			text: message
		})
	}
	return loading.value
}

/**
 * 关闭全屏loading
 */
export const closeLoading: () => void = () => {
	if (loading.value) {
		loading.value.close()
		loading.value = undefined
	}
}

/**
 * 格式化日期
 * @param dateLike
 * @param pattern
 */
export const formatDateTime: (dateLike?: number | string | Date, pattern?: string) => string = (dateLike, pattern = DEFAULT_PATTERN) => {
	if (!exist(dateLike)) {
		return ''
	}
	const date = new Date(dateLike!)
	// @ts-ignore
	return pattern.replace(SIGN_REGEXP, substring => {
		switch (substring.charAt(0)) {
			case 'y':
				return padWith0(date.getFullYear(), substring.length)
			case 'M':
				return padWith0(date.getMonth() + 1, substring.length)
			case 'd':
				return padWith0(date.getDate(), substring.length)
			case 'w':
				return date.getDay() + 1
			case 'h':
				return padWith0(date.getHours(), substring.length)
			case 'm':
				return padWith0(date.getMinutes(), substring.length)
			case 's':
				return padWith0(date.getSeconds(), substring.length)
			case 'S':
				return padWith0(date.getMilliseconds(), substring.length)
		}
	})
}

/**
 * 格式化日期<br/>
 * 用于el-table-column的formatter属性
 * @param row
 * @param column
 * @param cellValue
 * @param index
 */
export const elTableDateTimeFormatter: (row: any, column: TableColumnCtx<any>, cellValue: any, index: number) => string | VNode = (row, column, cellValue, index) => {
	return formatDateTime(cellValue)
}

/**
 * 从开头补0直至指定长度
 * @param s
 * @param len
 */
export const padWith0 = (s: number | string, len: number) => {
	len = len - String(s).length
	for (let i = 0; i < len; i++) {
		s = '0' + s
	}
	return s
}

/**
 * 首字母大写
 * @param str
 */
export const uppercaseFirst: (str: string) => string = str => {
	return str.charAt(0).toUpperCase() + str.slice(1)
}

/**
 * 检查HTMLElement是否含有某个class
 * @param {HTMLElement} ele
 * @param {string} cls
 * @return {boolean}
 */
export const hasClass: (ele: HTMLElement, cls: string) => boolean = (ele, cls) => {
	return !!ele.className.match(new RegExp('(\\s|^)' + cls + '(\\s|$)'))
}

/**
 * 向HTMLElement添加class
 * @param {HTMLElement} ele
 * @param {string} cls
 */
export const addClass: (ele: HTMLElement, cls: string) => void = (ele, cls) => {
	if (!hasClass(ele, cls)) ele.className += ' ' + cls
}

/**
 * 从HTMLElement删除class
 * @param {HTMLElement} ele
 * @param {string} cls
 */
export const removeClass: (ele: HTMLElement, cls: string) => void = (ele, cls) => {
	if (hasClass(ele, cls)) {
		const reg = new RegExp('(\\s|^)' + cls + '(\\s|$)')
		ele.className = ele.className.replace(reg, ' ')
	}
}

/**
 * 向HTMLElement添加或删除class
 * @param {HTMLElement} element
 * @param {string} className
 */
export function toggleClass(element: HTMLElement, className: string) {
	if (!element || !className) {
		return
	}
	let classString = element.className
	const nameIndex = classString.indexOf(className)
	if (nameIndex === -1) {
		classString += '' + className
	} else {
		classString =
				classString.substr(0, nameIndex) +
				classString.substr(nameIndex + className.length)
	}
	element.className = classString
}

/**
 * 加载网络css文件
 * @param url css资源url
 */
export function loadCss(url: string): void {
	const link = document.createElement('link')
	link.rel = 'stylesheet'
	link.href = url
	link.crossOrigin = 'anonymous'
	document.getElementsByTagName('head')[0].appendChild(link)
}

/**
 * 加载网络js文件
 * @param url js资源url
 */
export function loadJs(url: string): void {
	const link = document.createElement('script')
	link.src = url
	document.body.appendChild(link)
}

/**
 * 是否为外部链接
 * @param path
 */
export function isExternal(path: string): boolean {
	return /^(https?|ftp|mailto|tel):/.test(path)
}

/**
 * 根据pk字段的值从数组中获取key
 * @param arr
 * @param pk
 * @param value
 */
export const getArrayKey = (arr: any, pk: string, value: string): any => {
	for (const key in arr) {
		if (arr[key][pk] == value) {
			return key
		}
	}
	return false
}

/**
 * 表单重置
 * @param formEl
 */
export const onResetForm = (formEl: FormInstance | undefined) => {
	if (!formEl) return
	formEl.resetFields && formEl.resetFields()
}

interface ElTreeData {
	label: string
	children?: ElTreeData[]
}

/**
 * 将数据构建为ElTree的data {label:'', children: []}
 * @param data
 */
export const buildJsonToElTreeData = (data: any): ElTreeData[] => {
	if (typeof data == 'object') {
		const children = []
		for (const key in data) {
			children.push({
				label: key + ': ' + data[key],
				children: buildJsonToElTreeData(data[key])
			})
		}
		return children
	} else {
		return []
	}
}

/**
 * 是否为手机设备
 */
export const isMobile = () => {
	return !!navigator.userAgent.match(/android|webos|ip(hone|ad|od)|opera (mini|mobi|tablet)|iemobile|windows.+(phone|touch)|mobile|fennec|kindle (Fire)|Silk|maemo|blackberry|playbook|bb10; (touch|kbd)|Symbian(OS)|Ubuntu Touch/i)
}

/**
 * 从一个文件路径中获取文件名
 * @param path 文件路径
 */
export const getFileNameFromPath = (path: string) => {
	const paths = path.split('/')
	return paths[paths.length - 1]
}

/**
 * 根据运行环境获取基础请求URL
 */
export const getUrl = (): string => {
	const value: string = import.meta.env.VITE_BASE_URL as string
	return value == 'getCurrentDomain' ? window.location.protocol + '//' + window.location.host : value
}

export const usernameValidator: RuleItemValidator = (rule, value, callback) => {
	if (validateUsername(value)) {
		callback()
	} else {
		callback(Error())
	}
}

/**
 * 欧几里得算法（Euclidean Algorithm）求最大公约数
 * @param a
 * @param b
 */
export const gcd = (a: number, b: number): number => {
	return b === 0 ? a : gcd(b, a.mod(b))
}

/**
 * 求多个数的最大公约数（仅支持整数）
 * @param array
 */
export const greatestCommonDivisor = (array: number[]) => {
	if (!Array.isArray(array)) {
		throw Error('参数不是数组类型')
	}
	if (array.length === 0) {
		throw Error('数组为空')
	}
	// 两两求最大公约数
	while (array.length > 1) {
		array.push(gcd(array.pop()!, array.pop()!))
	}
	// 最后一个数即所求
	return array[0]
}

/**
 * 获取小数位数
 * @param num
 */
export const getDecimalDigit = (num: number) => {
	let split = num.toString().split('.')
	if (split.length === 1) {
		return 0
	}
	return split[1].length
}

/**
 * 将数组中的所有数都乘以一个倍数，使其变为整数，返回这个倍数的最小值
 * @param arr
 */
export const smallestMultiplyToInteger = (arr: number[]) => {
	if (!Array.isArray(arr)) {
		throw Error('参数不是数组类型')
	}
	if (arr.length === 0) {
		throw Error('数组为空')
	}
	let maxDecimalDigit = 0
	for (const arrElement of arr) {
		maxDecimalDigit = Math.max(maxDecimalDigit, getDecimalDigit(arrElement))
	}
	if (maxDecimalDigit === 0) {
		return 1
	}
	a:
			for (let i = 2; i <= Math.pow(10, maxDecimalDigit); i++) {
				for (const arrElement of arr) {
					if (getDecimalDigit(arrElement.mul(i)) !== 0) {
						continue a
					}
				}
				return i
			}
}

/**
 * 求传入数组元素的最小整数公倍数（支持小数）
 * 返回一个对象：result为最小公倍数，primeFactor是数组，为质因数
 * @param arr
 * @return {result: number, primeFactor: number[]}
 */
export const smallestCommonMultiple = (arr: number[]) => {
	if (!Array.isArray(arr)) {
		throw Error('参数不是数组类型')
	}
	if (arr.length === 0) {
		throw Error('数组为空')
	}
	const multiply = smallestMultiplyToInteger(arr)!
	const newArr = []
	for (let element of arr) {
		newArr.push(element.mul(multiply))
	}
	// 获取数组最大的数据，便于查找因数
	let maxNum = Math.max.apply(null, newArr)
	// 标记，初始值为真，为假时表示不是arr中任何数的因数
	let flag = true
	// 存放质因数
	const primeFactor = []
	// 存放累积
	let result = 1
	// 从2到maxNum寻找数据的因数
	for (let i = 2; i <= maxNum; i++) {
		// 当某个i不是任一个数据的因数时flag为false,进入下一次外层循环时，需将flag重置为true，以便进入while循环
		flag = true
		while (flag) {
			// 进入while循环，赋值false
			flag = false
			for (const key in newArr) {
				// 当某一项能被i整除，说明有独有或共有的因数i,将flag赋值true
				// 并将对应项短除
				if (newArr[key] % i === 0) {
					flag = true
					newArr[key] = Math.floor(newArr[key].div(i))
				}
			}
			// flag为true，说明i为共有或独有因数
			if (flag) {
				result = result.mul(i)
				primeFactor.push(i)
			}
		}
		// arr得到更新，外层循环maxNum也相应得到更新
		maxNum = Math.max.apply(null, newArr)
	}
	return {result: result, primeFactor: primeFactor}
}

/**
 * 用于el-table的sort-change事件<br/>
 * 后端排序，支持多列
 * @param args
 * @param orderByArray
 * @param callback
 */
export const elTableSortChange = (args: { column: TableColumnCtx<any>, prop: string, order: 'ascending' | 'descending' | null },
                                  orderByArray: OrderBy[],
                                  callback?: () => void) => {
	const column = args.column
	// 仅当该列的sortable为custom时才进行后端排序
	if (column.sortable !== 'custom') {
		return
	}
	const sortBy = column.sortBy
	// 需要排序的字段名
	let prop: string | undefined
	if (typeof sortBy === 'string') {
		prop = sortBy
	} else if (typeof sortBy === 'object' && Array.isArray(sortBy)) {
		// 后端排序不支持单列指定了多个排序字段的情况
		if (sortBy.length > 1) {
			prop = undefined
		} else {
			prop = sortBy[0]
		}
	} else if (typeof sortBy === 'function') {
		// 不支持后端排序
		prop = undefined
	} else {
		prop = args.prop
	}
	if (!prop) {
		return
	}
	// 字段名使用下划线命名法，与数据库字段名保持一致
	prop = snakeCase(prop)
	const order = args.order
	// 添加排序条件
	if (order) {
		if (!orderByArray) {
			orderByArray = []
		}
		let foundFlag = false
		for (const orderBy of orderByArray) {
			if (orderBy.field === prop) {
				orderBy.order = order
				foundFlag = true
			}
		}
		if (!foundFlag) {
			orderByArray.push({
				field: prop,
				order: order
			})
		}
	}
	// 删除排序条件
	else {
		if (Array.isArray(orderByArray) && orderByArray.length > 0) {
			remove(orderByArray, item => item.field === prop)
		}
	}
	callback?.()
}

/**
 * 用于el-table的header-cell-class-name属性<br/>
 * 设置除当前列之外的其他列的排序状态，用于支持多列排序
 * @param args
 * @param orderByArray
 */
export const elTableHeaderCellClassName = (args: { rows: any[], column: TableColumnCtx<any>, rowIndex: number, columnIndex: number },
                                           orderByArray: OrderBy[]) => {
	const column = args.column
	if (!orderByArray) {
		return
	}
	// 当前列需要排序的字段名
	let prop: string | undefined
	const sortBy = column.sortBy
	if (typeof sortBy === 'string') {
		prop = sortBy
	} else if (typeof sortBy === 'object' && Array.isArray(sortBy)) {
		// 后端排序不支持单列指定了多个排序字段的情况
		if (sortBy.length > 1) {
			prop = undefined
		} else {
			prop = sortBy[0]
		}
	} else if (typeof sortBy === 'function') {
		// 不支持后端排序
		prop = undefined
	} else {
		prop = column.property
	}
	if (!prop) {
		return
	}
	// 字段名使用下划线命名法，与数据库字段名保持一致
	prop = snakeCase(prop)
	// 设置其他列的排序状态
	for (const orderby of orderByArray) {
		if (prop === orderby.field) {
			column.order = orderby.order
			break
		}
	}
}

/**
 * 采矿机名称
 */
export const MinerNames = [
	'1级采矿机',
	'2级采矿机',
	'3级采矿机'
]

/**
 * 资源采集建筑名称，本质上指的是不需要原料就可以生产的建筑
 */
export const ResourceCollectorNames = [
	...MinerNames,
	'抽水站',
	'资源抽取器',
	'油井',
	'转化站'
]

/**
 * 判断某些配方是否包含所有采矿配方<br/>
 * @param recipeList
 */
export const containAllMinerRecipe = (recipeList: LoadOneStepVo[]) => {
	if (!recipeList || recipeList.length === 0) {
		return false
	}
	for (const minerName of MinerNames) {
		const some = recipeList.some(recipeVo => recipeVo.buildingVo?.buildingName === minerName)
		if (!some) {
			return false
		}
	}
	return true
}

/**
 * 判断某个配方是否为采集资源的配方<br/>
 * 这类配方没有原料，只有产物和生产建筑
 * @param recipe
 */
export const isResourceCollectionRecipe = (recipe: LoadOneStepVo) => {
	return (!recipe.materialList || recipe.materialList.length === 0) && !!recipe.productList && recipe.productList.length > 0 && !!recipe.buildingVo
}

/**
 * 如果是外部链接，则直接返回；否则拼接成OSS资源路径
 * @param avatar
 */
export const handleAvatarUrl = (avatar?: string) => {
	if (!avatar) {
		return undefined
	}
	if (isExternal(avatar)) {
		return avatar
	}
	return 'https://' + (isProd(import.meta.env.MODE) ? 'assets' : 'assets-dev') + '.my-satisfactory.cn/' + avatar
}

/**
 * 数字转百分比字符串
 */
export const numberToPercent = (num: number): string => {
	return new Decimal(num).mul(100).toNumber() + '%'
}
